/*
 Xern Entity Relocation Network
 Copyright (C) 2011 Ronie Salgado <roniesalg@gmail.com>

 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
 in the Software without restriction, including without limitation the rights
 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 copies of the Software, and to permit persons to whom the Software is
 furnished to do so, subject to the following conditions:

 The above copyright notice and this permission notice shall be included in
 all copies or substantial portions of the Software.

 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 THE SOFTWARE.
*/

#include "Xern/IResourceCache.hpp"
#include "Xern/DataFilter.hpp"
#include "Xern/Engine.hpp"
#include "Xern/Exceptions.hpp"
#include "Xern/Log.hpp"
#include "Xern/ResourceManager.hpp"

#include "ClientSession.hpp"
#include "TransferManager.hpp"

namespace XernMetaverse
{
	enum ImageCodec
	{
		IC_INVALID = 0,
		IC_RGB = 1,
		IC_J2C = 2,
		IC_BMP = 3,
		IC_TGA = 4,
		IC_JPEG = 5,
		IC_DXT = 6,
		IC_PNG = 7,
	};

	// DownloadState
	class DownloadState
	{
	public:
		DownloadState();
		~DownloadState();

		enum DownloadType
		{
			DT_IMAGE=0,
		};

		enum State
		{
			DS_WAIT_DESCRIPTION = 0,
			DS_WAIT_DATA,
		};

		typedef std::set<boost::shared_ptr<IResourceLoadedCallback> > Callbacks;

		uuid resourceId;
		DownloadType downloadType;
		State state;
		Callbacks callbacks;
		size_t size;
		int numpackets;
		int numpacketsLeft;
		int codec;
		std::string filename;
		boost::shared_ptr<IDataFilter> filter;
		std::vector<boost::shared_ptr<Message> > earlyBlocks;
	};

	DownloadState::DownloadState()
	{
		size = 0;
		numpackets = 0;
		codec = 0;
	}

	DownloadState::~DownloadState()
	{
	}

	// TransferManager
	TransferManager::TransferManager(boost::shared_ptr<ClientSession> session)
		: session(session)
	{
		// Create the message table.
		messageTable.Add("ImageData", &TransferManager::OnImageData, this);
		messageTable.Add("ImagePacket", &TransferManager::OnImagePacket, this);
	}

	TransferManager::~TransferManager()
	{
	}

	bool TransferManager::ProcessMessage(boost::shared_ptr<Message> message)
	{
		return messageTable[message->GetTemplate()->name](message);
	}

	void TransferManager::DownloadImage(const uuid &image, boost::shared_ptr<IResourceLoadedCallback> callback)
	{
		// Check if there's an already download.
		DownloadStates::iterator it = downloadStates.find(image);
		if(it != downloadStates.end())
		{
			// Append the callback into the existing download.
			boost::shared_ptr<DownloadState> state = it->second;
			state->callbacks.insert(callback);
			return;
		}

		// Try to load the image from the cache.
		boost::shared_ptr<ResourceManager> resourceMan = Engine::Get().GetResourceManager();
		boost::shared_ptr<IResource> resource = resourceMan->LoadFromCache(image);
		if(resource)
		{
			callback->OnLoadedResource(resource);
			return;
		}

		// Create a new download state.
		boost::shared_ptr<DownloadState> state(new DownloadState());
		state->resourceId = image;
		state->state = DownloadState::DS_WAIT_DESCRIPTION;
		state->downloadType = DownloadState::DT_IMAGE;
		state->callbacks.insert(callback);

		// Store the download state.
		downloadStates.insert(std::make_pair(image, state));

		// Create the request.
		boost::shared_ptr<Message> request = session->CreateMessage("RequestImage");

		// Write the agent data.
		MessageBlock &agentData = request->GetBlock("AgentData");
		agentData.GetVariable("AgentID").SetValue<uuid> (session->GetAgentId());
		agentData.GetVariable("SessionID").SetValue<uuid> (session->GetId());

		// Write the image data.
		MessageBlock &requestImage = request->GetBlock("RequestImage");
		requestImage.SetNumberOfSubBlocks(1);
		requestImage.GetVariable("Image", 0).SetValue<uuid> (image);
		requestImage.GetVariable("DiscardLevel", 0).SetValue<int> (0);
		requestImage.GetVariable("DownloadPriority", 0).SetValue<float> (1.0f);
		requestImage.GetVariable("Packet", 0).SetValue<unsigned int> (0);
		requestImage.GetVariable("Type", 0).SetValue<unsigned int> (0);

		// Perform the request.
		session->Send(request);
	}

	void TransferManager::OnImageData(boost::shared_ptr<Message> message)
	{
		// Image data.
		const MessageBlock &imageID = message->GetBlock("ImageID");
		uuid image = imageID.GetVariable("ID").GetValue<uuid> ();

		// Find the download state.
		DownloadStates::iterator it = downloadStates.find(image);
		if(it == downloadStates.end())
			return;

		boost::shared_ptr<DownloadState> state = it->second;

		state->size = imageID.GetVariable("Size").GetValue<unsigned int> ();
		state->numpackets = imageID.GetVariable("Packets").GetValue<unsigned int> ();
		state->codec = imageID.GetVariable("Codec").GetValue<unsigned int> ();

		// Get the resource manager.
		boost::shared_ptr<ResourceManager> resourceMan = Engine::Get().GetResourceManager();

		if(state->state == DownloadState::DS_WAIT_DESCRIPTION)
		{
			// Initialize the number of packets to download.
			state->numpacketsLeft = state->numpackets;

			// Select the extension.
			std::string ext;
			switch(state->codec)
			{
			case IC_RGB:
				ext = ".rgb";
				break;
			case IC_J2C:
				ext = ".j2c";
				break;
			case IC_BMP:
				ext = ".bmp";
				break;
			case IC_TGA:
				ext = ".tga";
				break;
			case IC_JPEG:
				ext = ".jpg";
				break;
			case IC_DXT:
				ext = ".dds";
				break;
			case IC_PNG:
				ext = ".png";
				break;
			case IC_INVALID:
			default:
				LogConsole->Error("Invalid/Unknown image codec id '%d'", state->codec);
				downloadStates.erase(it);
				break;
			}

			// Get the resource cache
			boost::shared_ptr<IResourceCache> cache = resourceMan->GetResourceCache();

			// Create the resource file name.
			state->filename = cache->CreateFileNameById(state->resourceId) + ext;
			cache->RegisterResourceId(state->resourceId, state->filename);

			//Create the resource file.
			FILE *out = fopen(state->filename.c_str(), "wb");
			state->filter.reset(new FileFilter(out, true));

			// Change the state.
			state->state = DownloadState::DS_WAIT_DATA;

			state->earlyBlocks.resize(state->numpackets-1);
		}

		// Read the first image payload.
		const MessageBlock &imageData = message->GetBlock("ImageData");
		const MessageVariable &data = imageData.GetVariable("Data");
		size_t packetLen = data.GetBufferSize();
		state->filter->Write(packetLen, data.GetBuffer());

		// Decrease the remainder packets.
		state->numpacketsLeft--;

		// Check if the image has been finished
		if(state->numpacketsLeft <= 0)
		{
			// Erase the download state.
			downloadStates.erase(it);

			try
			{
				// Flush the filter.
				state->filter->Flush();

				// Load the resource.
				boost::shared_ptr<IResource> image = resourceMan->LoadFromCache(state->filename);
				resourceMan->RenameResource(image, state->resourceId);

				// Perform the callbacks.
				DownloadState::Callbacks::iterator it = state->callbacks.begin();
				for(; it != state->callbacks.end(); it++)
				{
					boost::shared_ptr<IResourceLoadedCallback> callback = *it;
					callback->OnLoadedResource(image);
				}
			}
			catch(const XernException &e)
			{
				LogConsole->Error("Failed to load downloaded image: %s", e.what());
			}
		}
	}

	void TransferManager::OnImagePacket(boost::shared_ptr<Message> message)
	{
		// Image data.
		const MessageBlock &imageID = message->GetBlock("ImageID");
		uuid image = imageID.GetVariable("ID").GetValue<uuid> ();
		int packet = imageID.GetVariable("Packet").GetValue<unsigned int> ();

		// Find the download state.
		DownloadStates::iterator it = downloadStates.find(image);
		if(it == downloadStates.end())
			return;

		boost::shared_ptr<DownloadState> state = it->second;

		// Store the packet.
		state->earlyBlocks[packet-1] = message;

		// Check for early blocks.
		int nextPacket = state->numpackets - state->numpacketsLeft;
		if(state->state != DownloadState::DS_WAIT_DATA || packet != nextPacket)
			return;

		// Process also the early packets.
		do
		{
			// Get the packet.
			message = state->earlyBlocks[packet-1];

			// Get the data.
			const MessageBlock &imageData = message->GetBlock("ImageData");
			const MessageVariable &data = imageData.GetVariable("Data");

			// Write the data.
			size_t dataSize = data.GetBufferSize();
			state->filter->Write(dataSize, data.GetBuffer());

			// Remove the packet.
			state->earlyBlocks[packet-1] = boost::shared_ptr<Message> ();

			// Increase the packet.
			state->numpacketsLeft--;
			packet++;
		} while(state->numpacketsLeft > 0 && state->earlyBlocks[packet-1]);

		// Check if the image has been finished
		if(state->numpacketsLeft <= 0)
		{
			// Erase the download state.
			downloadStates.erase(it);

			try
			{
				// Flush the filter.
				state->filter->Flush();

				// Load the resource.
				boost::shared_ptr<ResourceManager> resourceMan = Engine::Get().GetResourceManager();
				boost::shared_ptr<IResource> image = resourceMan->LoadFromCache(state->filename);
				resourceMan->RenameResource(image, state->resourceId);

				// Perform the callbacks.
				DownloadState::Callbacks::iterator it = state->callbacks.begin();
				for(; it != state->callbacks.end(); it++)
				{
					boost::shared_ptr<IResourceLoadedCallback> callback = *it;
					callback->OnLoadedResource(image);
				}
			}
			catch(const XernException &e)
			{
				LogConsole->Error("Failed to load downloaded image: %s", e.what());
			}
		}
	}

}; // namespace XernMetaverse
